﻿/// <reference path="Scripts/jquery-2.0.2.min.js" />


var MineSweeper = (function () {
    "use strict";

    var Block = Class.create({
        constr: function (rowIndex, colIndex) {
            this.rowIndex = rowIndex;
            this.colIndex = colIndex;
            this.initContent = "";
            this.isMine = false;
            this.isOpen = false;
            this.adjacentMines = 0;
        },
        render: function () {
            var td = $("<td/>");
            td.text(this.initContent);
            return td;
        }
    });

    var Row = Class.create({
        constr: function (rowIndex, colsCount) {
            this.blocks = [];
            this.colsCount = colsCount;
            this.rowIndex = rowIndex;
            this.addBlocks();
        },
        addBlocks: function () {
            var block, colIndex;
            for (colIndex = 0; colIndex < this.colsCount; colIndex++) {
                block = new Block(this.rowIndex, colIndex);
                this.blocks.push(block);
            }
        },
        render: function () {
            var i, tr = $("<tr/>");
            for (i = 0; i < this.blocks.length; i++) {
                tr.append(this.blocks[i].render());
            }

            return tr;
        }
    });

    var Board = Class.create({
        constr: function (rowsCount, colsCount) {
            this.rows = [];
            this.rowsCount = rowsCount;
            this.colsCount = colsCount;
            this.addRows();
        },
        addRows: function () {
            var row, rowIndex;
            for (rowIndex = 0; rowIndex < this.rowsCount; rowIndex++) {
                row = new Row(rowIndex, this.colsCount);
                this.rows.push(row);
            }
        },
        render: function () {
            var i, table = $("<table/>").addClass("gameBoard");
            for (i = 0; i < this.rows.length; i++) {
                table.append(this.rows[i].render());
            }

            return table;
        },

        showAllMines: function () {
            var rowIndex, colIndex;
            for (rowIndex = 0; rowIndex < this.rowsCount; rowIndex++) {
                for (colIndex = 0; colIndex < this.colsCount; colIndex++) {

                    if (this.rows[rowIndex].blocks[colIndex].isMine) {
                        $(this.rows[rowIndex].blocks[colIndex]).addClass("mine");
                    }
                }
            }
        }
    });

    var Minesweeper = Class.create({
        constr: function (element, rowsCount, colsCount, minesCount) {
            this.element = element;
            this.board = new Board(rowsCount, colsCount);
            this.rowsCount = rowsCount;
            this.colsCount = colsCount;
            this.minesCount = minesCount;
            this.putMines();
            this.calculateAdjacentMines();
            this.colors = ["#0100FE", "#017F01", "#FE0000", "#010080", "#810102", "#008081", "#000000", "#808080"];



            this.blocksToOpen = rowsCount * colsCount - minesCount;
            this.remainingMines = minesCount;
            $("#mines-remain").text(this.remainingMines);
        },
        putMines: function () {
            var currentMines = 0, block, row, col;

            while (currentMines < this.minesCount) {
                row = Math.floor((Math.random() * this.board.rowsCount));
                col = Math.floor((Math.random() * this.board.colsCount));
                block = this.board.rows[row].blocks[col];

                if (block.isMine === false) {
                    block.isMine = true;
                    currentMines++;
                }
            }
        },
        calculateAdjacentMines: function () {
            var dirRow = [-1, -1, 0, 1, 1, 1, 0, -1];
            var dirCol = [0, 1, 1, 1, 0, -1, -1, -1];
            var block, rowIndex, colIndex, dir, adjRowIndex, adjColIndex;

            for (rowIndex = 0; rowIndex < this.board.rowsCount; rowIndex++) {
                for (colIndex = 0; colIndex < this.board.colsCount; colIndex++) {
                    block = this.board.rows[rowIndex].blocks[colIndex];

                    if (block.isMine) {
                        continue;
                    }

                    for (dir = 0; dir < dirRow.length; dir++) {
                        adjRowIndex = rowIndex + dirRow[dir];
                        adjColIndex = colIndex + dirCol[dir];

                        if (this.isInBoardRange(adjRowIndex, adjColIndex) && this.board.rows[adjRowIndex].blocks[adjColIndex].isMine) {
                            block.adjacentMines++;
                        }
                    }
                }
            }
        },
        isInBoardRange: function (rowIndex, colIndex) {
            if ((0 <= rowIndex && rowIndex < this.board.rowsCount) && (0 <= colIndex && colIndex < this.board.colsCount)) {
                return true;
            }

            return false;
        },
        checkBlockType: function (rowIndex, colIndex) {
            var cell, block = this.board.rows[rowIndex].blocks[colIndex];

            if (block.isMine) {

                // Game over
                this.showAllMines();
                var time = $("#inner-time").text();
                $("#level").text("Lost in " + time + ", Choose another game");
                $("#begin-game").slideDown(1000);
                this.showAllMines(rowIndex, colIndex);
                $.playSound('Sounds/explode.mp3');

            }
            else if (this.blocksToOpen - 1 == 0) {

                cell = this.getCellByGivenCoordinates(rowIndex, colIndex);
                cell.text(block.adjacentMines);
                cell.css('color', this.colors[block.adjacentMines - 1]);

                this.won();

                console.log("won");
            }
            else if (block.adjacentMines > 0) {
                cell = this.getCellByGivenCoordinates(rowIndex, colIndex);
                cell.text(block.adjacentMines);
                cell.css('color', this.colors[block.adjacentMines - 1]);
                $.playSound('Sounds/click.mp3');

                this.blocksToOpen--;
                console.log(this.blocksToOpen);
            }
            else {
                this.openAllEmptyAdjacentBlocks(rowIndex, colIndex);
                $.playSound('Sounds/opening.mp3');
            }
        },
        getCellByGivenCoordinates: function (rowIndex, colIndex) {
            var cell = $(this.element).first().find('tr').eq(rowIndex).find('td').eq(colIndex);
            return cell;
        },
        openAllEmptyAdjacentBlocks: function (rowIndex, colIndex) {
            var dirRow = [-1, -1, 0, 1, 1, 1, 0, -1];
            var dirCol = [0, 1, 1, 1, 0, -1, -1, -1];
            var currentBlock, adjacentBlock, cell, adjRowIndex, adjColIndex, i;

            var queue = new Queue();
            queue.enqueue(this.board.rows[rowIndex].blocks[colIndex]);

            while (queue.length() > 0) {
                currentBlock = queue.dequeue();

                this.blocksToOpen--;
                console.log(this.blocksToOpen + "here");

                for (i = 0; i < dirRow.length; i++) {
                    adjRowIndex = currentBlock.rowIndex + dirRow[i];
                    adjColIndex = currentBlock.colIndex + dirCol[i];

                    if (this.isInBoardRange(adjRowIndex, adjColIndex)) {
                        adjacentBlock = this.board.rows[adjRowIndex].blocks[adjColIndex];

                        if (adjacentBlock.adjacentMines === 0) {
                            cell = this.getCellByGivenCoordinates(adjacentBlock.rowIndex, adjacentBlock.colIndex);


                            if (cell.hasClass("open")) {
                                continue;
                            }
                            // I think if we have open block after this it's pointless

                            cell.removeClass();
                            cell.addClass('open');
                            adjacentBlock.adjacentMines = -1;
                            queue.enqueue(adjacentBlock);

                            //this.blockToOpen--;
                            //console.log(this.blockToOpen);
                        }
                        else if (adjacentBlock.adjacentMines > 0) {
                            cell = this.getCellByGivenCoordinates(adjacentBlock.rowIndex, adjacentBlock.colIndex);

                            if (cell.hasClass("open")) {
                                continue;
                            }
                            // I think if we have open block after this it's pointless

                            cell.removeClass();
                            cell.addClass('open');
                            cell.text(adjacentBlock.adjacentMines);
                            cell.css('color', this.colors[adjacentBlock.adjacentMines - 1]);

                            this.blocksToOpen--;
                            console.log(this.blocksToOpen + "->1");
                        }
                    }
                }
            }

            if (this.blocksToOpen == 0) {
                this.won();
                console.log("won")
            }
        },
        addEvents: function () {
            var that = this;
            $("td").on("click", function () {
                var clickedBlock = $(this);

                if (clickedBlock.hasClass("open")) {
                    return;
                }
                if (clickedBlock.hasClass("mine")) {
                    return;
                }

                clickedBlock.removeClass();
                clickedBlock.addClass("open");

                // Get coordinates of clicked block
                var colIndex = clickedBlock.parent().children().index($(this));
                var rowIndex = clickedBlock.parent().parent().children().index($(this).parent());
                that.checkBlockType(rowIndex, colIndex);
            });
            $("td").on("contextmenu", function (event) {
                event.preventDefault();
                var clickedBlock = $(this);
                if (clickedBlock.hasClass("open") || clickedBlock.hasClass("mine")) {
                    return;
                }

                $.playSound('Sounds/flag.mp3');

                if (clickedBlock.hasClass("flag")) {
                    clickedBlock.removeClass("flag");
                    clickedBlock.addClass("question");

                    that.remainingMines++;
                    $("#mines-remain").text(that.remainingMines);
                    return;
                }

                if (clickedBlock.hasClass("question")) {
                    clickedBlock.removeClass("question");
                    return;
                }

                clickedBlock.addClass("flag");

                if (that.remainingMines > 0) {
                    that.remainingMines--;
                    $("#mines-remain").text(that.remainingMines);
                }

            });
        },
        startGame: function () {
            var gameBoard = this.board.render();
            $(this.element).append(gameBoard);
            this.addEvents();
        },
        removeBoard: function () {
            $(this.element).children().filter(".gameBoard").remove();
        },

        showAllMines: function (rowIndex, colIndex) {
            var rowIndex, colIndex, cell;
            for (rowIndex = 0; rowIndex < this.rowsCount; rowIndex++) {
                for (colIndex = 0; colIndex < this.colsCount; colIndex++) {
                    if (this.board.rows[rowIndex].blocks[colIndex].isMine) {
                        cell = this.getCellByGivenCoordinates(rowIndex, colIndex);
                        cell.addClass("mine");
                    }
                }
            }
        },

        won: function () {
            this.showAllMines();
            var time = $("#inner-time").text();
            $("#level").text("WON in " + time + ", Choose another game");
            $("#begin-game").slideDown(1000);
        }
    });

    return Minesweeper;
})();

var Timer = (function () {
    "use strict";

    var Timer = Class.create({
        constr: function (element) {
            this.element = $(element);
            this.seconds = 0;
            this.timerId;
            //this.element.text(0);
        },
        start: function () {
            var that = this;
            this.element.parent().show();

            if (!this.timerId) {
                this.timerId = window.setInterval(function () {
                    that.seconds++;
                    that.element.text(that.seconds);
                }, 1000);
            }
        },
        stop: function () {
            window.clearInterval(this.timerId);
            this.timerId = null;
        },
        clear: function () {
            this.seconds = 0;
            this.element.text(0);
            this.stop();
        },
        restart: function () {
            this.seconds = 0;

            window.clearInterval(this.timerId);
            this.start();
        },
        getSeconds: function () {
            return this.seconds;
        }
    });

    return Timer;
})();

var ScoreBoard = (function () {
    "use strict";

    var Score = Class.create({
        constr: function (name, score) {
            this.name = name;
            this.score = score;
        },
        render: function () {
            var $element = $("<li/>");
            $element.text(this.name + ": " + this.score);
            return $element;
        }
    });

    var ScoreList = Class.create({
        constr: function (element) {
            this.currentList = $("<ol/>");
            this.results = [];
            $(element).append(this.currentList);
        },
        render: function () {
            this.currentList.children().remove();
            this.sort();

            for (var i = 0; i < this.results.length; i++) {
                var score = this.results[i].render();
                this.currentList.append(score);
            }
        },
        add: function (name, score) {
            var score = new Score(name, score);
            this.results.push(score);
            this.render();
        },
        sort: function () {
            this.results.sort(function (a, b) {
                return b.score - a.score;
            })
        }
    });

    return ScoreList;
})();

var Queue = Class.create({
    constr: function () {
        this.arr = [];
    },
    enqueue: function (elem) {
        this.arr.push(elem);
    },
    dequeue: function () {
        var retValue = this.arr[0];
        var newArr = new Array(this.arr.length - 1);
        for (var i = 0; i < newArr.length; i++) {
            newArr[i] = this.arr[i + 1];
        }

        this.arr = newArr;
        return retValue;
    },
    length: function () {
        return this.arr.length;
    }
});


var board = document.getElementById("board");
var timer = document.getElementById("inner-time");

//var minesweeper = new MineSweeper(board, 16, 16, 28);
var minesweeper;
var timer = new Timer(timer)
$("#begin-game").slideDown(1000);
$("#easy").on("click", function () {
    minesweeper = new MineSweeper(board, 3, 3, 1);
    Start(minesweeper);
});

$("#medium").on("click", function () {
    minesweeper = new MineSweeper(board, 12, 20, 4);
    Start(minesweeper);
});

$("#hard").on("click", function () {
    minesweeper = new MineSweeper(board, 20, 30, 100);
    Start(minesweeper);
});

function Start(minesweeper) {
    minesweeper.removeBoard()
    minesweeper.startGame();
    timer.clear();
    timer.start();
    $("#begin-game").slideUp(1000);
}



//minesweeper.start();
//minesweeper.removeBoard();
//minesweeper.start();